/*
 *
 * Created on: 22.09.2012
 *
 * config.cpp
 * This file is part of KOBUS
 * 
 * Copyright (C) 2012 - Ivan Penkin, MIPT
 * grek.penkin@gmail.com
 *
 * KOBUS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * KOBUS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with KOBUS; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 *  
 */

#include <config.h>
#include <config_p.h>

#include <qdaemonapplication.h>

#include <qtextstream.h>
#include <qstringlist.h>

#include <errno.h>



using namespace kobus;


ConfigPrivate::ConfigPrivate(
                   const QString & cfgPath, QObject * parent)
    :QObject(parent),
     d_path(cfgPath) {

}


ConfigPrivate::~ConfigPrivate() {

}


void ConfigPrivate::read() throw (Exception) {

  Log log;

  QFile config(d_path);
  if (!config.open(QIODevice::ReadOnly))
    throw Exception(config.error(), config.errorString());

  int lineCounter = 0;
  QTextStream configStream(&config);
  QString line;
  QString section;

  const QRegExp sectionRegExp("^[\\[]{1}[(\\-|\\w|\\d)]+[\\]]{1}$");
  const QRegExp keyRegExp("^[\\s]*[(\\-|\\w|\\d|)]+[\\s]*$");
  const QRegExp valueRegExp("^[\\s]*[^#@!~`<>%'\";:]+[\\s]*$");
  //("^[\\s]*[(\\/|\\.|(\\,\\s)|\\,|\\w|\\d|\\|\\{|\\}|\\(|\\)|\\^|\\$|\\?|\\*|\\+\\-)]+[\\s]*$");

  while (!configStream.atEnd()) {

    lineCounter++;
    line = configStream.readLine().trimmed().simplified();

    if (line.isEmpty() || line.at(0) == '#') {
      continue;
    } else if (line.contains(sectionRegExp)) {
      section = line.remove(line.length() - 1, 1).remove(0, 1);
      continue;
    }

    QStringList statement = line.split('=');
    if (statement.count() != 2
          || !statement[0].contains(keyRegExp)
          || !statement[1].contains(valueRegExp)) {
#ifdef _DEBUG_LEVEL
      log.writeDebug(Log::EXTRA, "statement.count() = %d", statement.count());

      if (statement.count() == 2) {
        log.writeDebug(Log::EXTRA, "statement[0] = '"+ statement[0] + "'");
        log.writeDebug(Log::EXTRA, "statement[1] = '"+ statement[1] + "'");
      }
#endif
      throw Exception(EINVAL,
                "\'" + d_path + "\"" + ": " +
                "Wrong statement at line: " +
                QString().number(lineCounter, 10) + ".");
    } else {
      QString key = section + "." + statement[0].trimmed();
#ifdef _DEBUG_LEVEL
//      log.writeDebug(Log::EXTRA, "ConfigPrivate::read() key: " + key);
//      log.writeDebug(Log::EXTRA, "ConfigPrivate::read() value: " + statement[1].trimmed());
#endif

      if (d_contents.contains(key))
          throw Exception(EINVAL,
                    "\'" + d_path + "\"" + ": " +
                    "Key doubling was found: \"" + key + "\" at line" +
                    QString().number(lineCounter, 10) + ".");
      d_contents.insert(key, statement[1].trimmed());
    }
  }

}

bool ConfigPrivate::contains(const QString & key) const {

  return d_contents.contains(key);
}


const QString ConfigPrivate::getString(const QString & key)
                                 const throw(Exception) {

  if (d_contents.contains(key)) {
    return d_contents.value(key);
  } else {
    throw Exception(
              EINVAL,
              "There is no string associated with key='" + key + "'.");
  }
}


QList<QString> ConfigPrivate::keys() const {

  return d_contents.keys();
}


ConfigPrivate * Config::d_ptr = NULL;

Config::Config(QObject * parent)
    :QObject(parent) {

}


Config::Config(const QString & path, QObject * parent)
    :QObject(parent){

  if (!d_ptr) {
    QObject * app = QDaemonApplication::instance();

    if (app)
      d_ptr = new ConfigPrivate(path, app);
    else
      d_ptr = new ConfigPrivate(path);
  }
}


Config::Config(ConfigPrivate * dd, QObject * parent)
    :QObject(parent) {

  d_ptr = dd;
}

Config::~Config() {

}


void Config::read() throw (Exception) {

  Q_D(Config);
  return d -> read();
}

bool Config::contains(const Key & key) const {

  Q_D(const Config);
  return d -> contains(key.sectionName() + "." + key.getName());
}


const QString Config::getString(const Key & key) const throw (Exception) {

  Q_D(const Config);
  return d -> getString(key.sectionName() + "." + key.getName());
}


int Config::getInt(const Config::Key & key) const throw (Exception) {

  bool conversionResult = false;
  int result = getString(key).toInt(&conversionResult);

  if (!conversionResult) {
    throw Exception(EINVAL, "Value of '" + key.sectionName() + "."
            + key.getName() + "' is not a number");
  }
  return result;
}


QList<QString> Config::keys() const {

  Q_D(const Config);
  return d -> keys();
}
